///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_ALGORITHM_DETECT_H_
#define _BUOLA_IMAGE_ALGORITHM_DETECT_H_

#include <buola/image/algorithm/detail/opencv.h>

namespace buola { namespace img {

//! \name detection
//@{

inline void image_value_map(const CView_gray32f &pSrc,const CMView_gray8 &pDst,
                            double pMin,double pMax)
{
    double lMult=255.0/(pMax-pMin);
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.RowBegin(y);
        auto lDstIt=pDst.RowBegin(y);

        for(int x=0;x<pSrc.Width();++x)
        {
            double lValue=(lSrcIt[x]-pMin)*lMult;

            if(lValue<=0.0)
                lDstIt[x]=0;
            else if(lValue>=255.0)
                lDstIt[x]=255;
            else
                lDstIt[x]=(unsigned char)lValue;
        }
    }
}

template<typename tView>
inline typename tView::TValue image_max(const tView &pI)
{
    typedef typename tView::TValue tType;
    tType lMax=std::numeric_limits<tType>::min();

    for(int y=0;y<pI.Height();++y)
    {
        auto lEnd=pI.RawRowEnd(y);

        for(auto lIt=pI.RawRowBegin(y);lIt!=lEnd;++lIt)
        {
            lMax=std::max(lMax,*lIt);
        }
    }

    return lMax;
}

template<typename tView>
inline typename tView::TValue image_min(const tView &pI)
{
    typedef typename tView::TValue tType;
    tType lMin=std::numeric_limits<tType>::max();

    for(int y=0;y<pI.Height();++y)
    {
        auto lEnd=pI.RawRowEnd(y);

        for(auto lIt=pI.RawRowBegin(y);lIt!=lEnd;++lIt)
        {
            lMin=std::min(lMin,*lIt);
        }
    }

    return lMin;
}

template<typename tView>
inline bool motion_detect(const tView &p1,const tView &p2,
                    double pThreshold=2.0)
{
    //this should be a big int for integer,double for real
    double lDiff=0;

    for(int y=0;y<p1.Height();++y)
    {
        auto l1It=p1.RawRowBegin(y);
        auto l2It=p2.RawRowBegin(y);
        auto l1End=p1.RawRowEnd(y);

        for(;l1It!=l1End;++l1It,++l2It)
        {
            lDiff+=std::abs(double(*l1It-*l2It));
        }
    }

    double lNormDiff=double(lDiff)/double(p1.Width()*p1.Height()*tView::cChannels);

    return (lNormDiff>pThreshold);
}

template<typename tView>
inline bool motion_detect_sq(const tView &p1,const tView &p2,
                    double pThreshold=2.0)
{
    //this should be a big int for integer,double for real
    double lDiff=0;

    for(int y=0;y<p1.Height();++y)
    {
        auto l1It=p1.RawRowBegin(y);
        auto l2It=p2.RawRowBegin(y);
        auto l1End=p1.RawRowEnd(y);

        for(;l1It!=l1End;++l1It,++l2It)
        {
            double lD=double(*l1It-*l2It);
            lDiff+=lD*lD;
        }
    }

    double lNormDiff=double(lDiff)/double(p1.Width()*p1.Height()*tView::cChannels);

    return (lNormDiff>pThreshold*pThreshold);
}

template<typename tView1,typename tView2>
inline void image_accum(const tView1 &pSrc,const tView2 &pDst)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.RawRowBegin(y);
        auto lDstIt=pDst.RawRowBegin(y);
        auto lSrcEnd=pSrc.RawRowEnd(y);

        for(;lSrcIt!=lSrcEnd;++lSrcIt,++lDstIt)
        {
            *lDstIt+=*lSrcIt;
        }
    }
}

template<typename tView,typename tScalar>
inline void image_divide(const tView &pI,tScalar pScalar)
{
    for(int y=0;y<pI.Height();++y)
    {
        auto lEnd=pI.RawRowEnd(y);

        for(auto lIt=pI.RawRowBegin(y);lIt!=lEnd;++lIt)
        {
            *lIt/=pScalar;
        }
    }
}

template<typename tView>
inline void image_log(const tView &pI)
{
    for(int y=0;y<pI.Height();++y)
    {
        auto lEnd=pI.RawRowEnd(y);

        for(auto lIt=pI.RawRowBegin(y);lIt!=lEnd;++lIt)
        {
            *lIt=(unsigned char)
                    ((log((*lIt+1.0)/256.0)+5.546)*(255.0/5.546));
        }
    }
}

template<typename tPixelSrc,typename tPixelDst>
inline void pixel_diff_hsv(const tPixelSrc &p1,
            const tPixelSrc &p2,const tPixelDst &pDst)
{
    float lHue=std::abs(p1[0]-p2[0]);

    if(lHue>0.5)
        lHue=1.0f-lHue;

    pDst[0]=lHue;
    pDst[1]=std::abs(p1[1]-p2[1]);
    pDst[2]=std::abs(p1[2]-p2[2]);
}

inline void image_diff(const CView_hsv32f &p1,const CView_hsv32f &p2,const CMView_hsv32f &pDst)
{
    msg_info() << "using hsv32 version" << "\n";

    for(int y=0;y<p1.Height();++y)
    {
        auto l1It=p1.RowBegin(y);
        auto l2It=p2.RowBegin(y);
        auto lDstIt=pDst.RowBegin(y);

        for(int x=0;x<p1.Width();++x)
        {
            pixel_diff_hsv(l1It[x],l2It[x],lDstIt[x]);
        }
    }
}

inline void max_component(const CView_rgb8 &pSrc,const CMView_gray8 &pDst)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.RowBegin(y);
        auto lDstIt=pDst.RowBegin(y);

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x]=std::max({lSrcIt[x][0],lSrcIt[x][1],lSrcIt[x][2]});
        }
    }
}

inline void min_component(const CView_rgb8 &pSrc,const CMView_gray8 &pDst)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.RowBegin(y);
        auto lDstIt=pDst.RowBegin(y);

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x]=std::min({lSrcIt[x][0],lSrcIt[x][1],lSrcIt[x][2]});
        }
    }
}

inline void threshold_hsv_image_diff(const CView_hsv32f &pSrc,
          const CView_hsv32f &pDiff,const CMView_gray8 &pDst,
                float pHThresh=0.09,float pSThresh=0.1,float pVThresh=0.25)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.RowBegin(y);
        auto lDiffIt=pDiff.RowBegin(y);
        auto lDstIt=pDst.RowBegin(y);

        for(int x=0;x<pSrc.Width();++x)
        {
            if(lDiffIt[x][2]>pVThresh||
               (lSrcIt[x][2]>0.05&&(lDiffIt[x][1]>pSThresh||(lSrcIt[x][1]>0.05&&lSrcIt[x][0]>pHThresh))))
            {
                lDstIt[x]=255;
            }
            else
            {
                lDstIt[x]=0;
            }
        }
    }
}

//@}
    
/*namespace image*/ } /*namespace buola*/ }

#endif
